import applyMixin from './mixin'
import devtoolPlugin from './plugins/devtool'
import ModuleCollection from './module/module-collection'
import { forEachValue, isObject, isPromise, assert, partial } from './util'

let Vue // bind on install


/**
 * 我们在使用 Vuex 的时候，通常会实例化 Store 类，然后传入一个对象，包括我们定义好的 actions、getters、mutations、state等，
 * 甚至当我们有多个子模块的时候，我们可以添加一个 modules 对象
*/
export class Store {
  constructor (options = {}) {
    /** 
     * 对 Vue 的判断主要是保证 install 方法只执行一次，这里把 install 方法的参数 _Vue 对象赋值给 Vue 变量，
     * 这样我们就可以在 index.js 文件的其它地方使用 Vue 这个变量了
     * */
    if (!Vue && typeof window !== 'undefined' && window.Vue) {
      install(window.Vue)
    }
    //“断言函数”，来判断是否满足一些条件。
    if (__DEV__) {
      //确保 Vue 的存在，也就是在我们实例化 Store 之前，必须要保证之前的 install 方法已经执行了
      assert(Vue, `must call Vue.use(Vuex) before creating a store instance.`)
      /**
       * 确保 Promsie 可以使用的，因为 Vuex 的源码是依赖 Promise 的。
       * Promise 是 es6 提供新的 API，由于现在的浏览器并不是都支持 es6 语法的，所以通常我们会用 babel 编译我们的代码，
       * 如果想使用 Promise 这个 特性，我们需要在 package.json 中添加对 babel-polyfill 的依赖并在代码的入口加上import 'babel-polyfill' 这段代码
       * */
      assert(typeof Promise !== 'undefined', `vuex requires a Promise polyfill in this browser.`)
      assert(this instanceof Store, `store must be called with the new operator.`)
    }
    /** 
     * 利用 es6 的结构赋值拿到 options 里的 state，plugins 和 strict。
     * state 表示 rootState，plugins 表示应用的插件、strict 表示是否开启严格模式。
     * */
    const {
      plugins = [],
      strict = false
    } = options

    // store internal state
    this._committing = false //标志一个提交状态，作用是保证对 Vuex 中 state 的修改只能在 mutation 的回调函数中，而不能在外部随意修改 state
    this._actions = Object.create(null) //用来存储用户定义的所有的 actions
    this._actionSubscribers = [] //用来存储所有对 actions 变化的订阅者
    this._mutations = Object.create(null) //用来存储用户定义所有的 mutatins
    this._wrappedGetters = Object.create(null) //用来存储用户定义的所有 getters
    this._modules = new ModuleCollection(options) //用来存储所有的运行时的 modules
    this._modulesNamespaceMap = Object.create(null)
    this._subscribers = [] //用来存储所有对 mutation 变化的订阅者
    this._watcherVM = new Vue() //是一个 Vue 对象的实例，主要是利用 Vue 实例方法 $watch 来观测变化的
    this._makeLocalGettersCache = Object.create(null)

    //把 Store 类的 dispatch 和 commit 的方法的 this 指针指向当前 store 的实例上
    const store = this
    const { dispatch, commit } = this
    this.dispatch = function boundDispatch (type, payload) {
      return dispatch.call(store, type, payload)
    }
    this.commit = function boundCommit (type, payload, options) {
      return commit.call(store, type, payload, options)
    }

    //表示是否开启严格模式，在严格模式下会观测所有的 state 的变化，建议在开发环境时开启严格模式，线上环境要关闭严格模式，否则会有一定的性能开销
    this.strict = strict

    const state = this._modules.root.state

    //初始化根模块。
    //这也会递归地注册所有子模块
    //并收集this._wrappedGetters中的所有模块的getters
    //installModule 方法是把我们通过 options 传入的各种属性模块注册和安装
    installModule(this, state, [], this._modules.root)

    //初始化 store._vm，观测 state 和 getters 的变化
    resetStoreVM(this, state)

    // apply plugins 应用传入的插件
    plugins.forEach(plugin => plugin(this))

    const useDevtools = options.devtools !== undefined ? options.devtools : Vue.config.devtools
    if (useDevtools) {
      devtoolPlugin(this)
    }
  }

  get state () {
    return this._vm._data.$$state
  }

  set state (v) {
    if (__DEV__) {
      assert(false, `use store.replaceState() to explicit replace store state.`)
    }
  }

  /**
   * type 表示 mutation 的类型，payload 表示额外的参数，options 表示一些配置，比如 silent 等，稍后会用到。
   * commit 函数首先对 type 的类型做了判断，处理了 type 为 object 的情况，接着根据 type 去查找对应的 mutation，如果找不到，则输出一条错误信息，
   * 否则遍历这个 type 对应的 mutation 对象数组，执行 handler(payload) 方法，这个方法就是之前定义的 wrappedMutationHandler(handler)，
   * 执行它就相当于执行了 registerMutation 注册的回调函数，并把当前模块的 state 和 额外参数 payload 作为参数传入。
   * 注意这里我们依然使用了 this._withCommit 的方法提交 mutation。
   * commit 函数的最后，判断如果不是静默模式，则遍历 this._subscribers数组（subscribe 方法），调用回调函数，并把 mutation 和当前的根 state 作为参数传入。
   * 
  */
  commit (_type, _payload, _options) {
    // check object-style commit
    const {
      type,
      payload,
      options
    } = unifyObjectStyle(_type, _payload, _options)

    const mutation = { type, payload }
    const entry = this._mutations[type]
    if (!entry) {
      if (__DEV__) {
        console.error(`[vuex] unknown mutation type: ${type}`)
      }
      return
    }
    this._withCommit(() => {
      entry.forEach(function commitIterator (handler) {
        handler(payload)
      })
    })

    this._subscribers
      .slice() // shallow copy to prevent iterator invalidation if subscriber synchronously calls unsubscribe
      .forEach(sub => sub(mutation, this.state))

    if (
      __DEV__ &&
      options && options.silent
    ) {
      console.warn(
        `[vuex] mutation type: ${type}. Silent option has been removed. ` +
        'Use the filter functionality in the vue-devtools'
      )
    }
  }
  //type 表示 action 的类型，payload 表示额外的参数。前面几行代码和 commit 接口非常类似，都是找到对应 type 下的 action 对象数组，
  //唯一和 commit 不同的地方是最后部分，它对 action 的对象数组长度做判断，如果长度为 1 则直接调用 entry[0](payload)， 
  //这个方法就是之前定义的 wrappedActionHandler(payload, cb)，执行它就相当于执行了 registerAction 注册的回调函数，
  //并把当前模块的 context 和 额外参数 payload 作为参数传入。
  //所以我们在 action 的回调函数里，可以拿到当前模块的上下文包括 store 的 commit 和 dispatch 方法、getter、当前模块的 state 和 rootState，可见 action 是非常灵活的
  dispatch (_type, _payload) {
    // check object-style dispatch
    const {
      type,
      payload
    } = unifyObjectStyle(_type, _payload)

    const action = { type, payload }
    const entry = this._actions[type]
    if (!entry) {
      if (__DEV__) {
        console.error(`[vuex] unknown action type: ${type}`)
      }
      return
    }

    try {
      this._actionSubscribers
        .slice() // shallow copy to prevent iterator invalidation if subscriber synchronously calls unsubscribe
        .filter(sub => sub.before)
        .forEach(sub => sub.before(action, this.state))
    } catch (e) {
      if (__DEV__) {
        console.warn(`[vuex] error in before action subscribers: `)
        console.error(e)
      }
    }

    const result = entry.length > 1
      ? Promise.all(entry.map(handler => handler(payload)))
      : entry[0](payload)

    return new Promise((resolve, reject) => {
      result.then(res => {
        try {
          this._actionSubscribers
            .filter(sub => sub.after)
            .forEach(sub => sub.after(action, this.state))
        } catch (e) {
          if (__DEV__) {
            console.warn(`[vuex] error in after action subscribers: `)
            console.error(e)
          }
        }
        resolve(res)
      }, error => {
        try {
          this._actionSubscribers
            .filter(sub => sub.error)
            .forEach(sub => sub.error(action, this.state, error))
        } catch (e) {
          if (__DEV__) {
            console.warn(`[vuex] error in error action subscribers: `)
            console.error(e)
          }
        }
        reject(error)
      })
    })
  }
  /**
   * 他接受的参数是一个回调函数，会把这个回调函数保存到 this._subscribers 上，并返回一个函数，
   * 当我们调用这个返回的函数，就可以解除当前函数对 store 的 mutation 的监听。
   * 其实，Vuex 的内置 logger 插件就是基于 subscribe 接口实现对 store 的 muation的监听，稍后我们会详细介绍这个插件
   * 
  */
  subscribe (fn, options) {
    return genericSubscribe(fn, this._subscribers, options)
  }

  subscribeAction (fn, options) {
    const subs = typeof fn === 'function' ? { before: fn } : fn
    return genericSubscribe(subs, this._actionSubscribers, options)
  }

  watch (getter, cb, options) {
    if (__DEV__) {
      assert(typeof getter === 'function', `store.watch only accepts a function.`)
    }
    return this._watcherVM.$watch(() => getter(this.state, this.getters), cb, options)
  }

  replaceState (state) {
    this._withCommit(() => {
      this._vm._data.$$state = state
    })
  }

  registerModule (path, rawModule, options = {}) {
    if (typeof path === 'string') path = [path]

    if (__DEV__) {
      assert(Array.isArray(path), `module path must be a string or an Array.`)
      assert(path.length > 0, 'cannot register the root module by using registerModule.')
    }

    this._modules.register(path, rawModule)
    installModule(this, this.state, path, this._modules.get(path), options.preserveState)
    // reset store to update getters...
    resetStoreVM(this, this.state)
  }

  unregisterModule (path) {
    if (typeof path === 'string') path = [path]

    if (__DEV__) {
      assert(Array.isArray(path), `module path must be a string or an Array.`)
    }

    this._modules.unregister(path)
    this._withCommit(() => {
      const parentState = getNestedState(this.state, path.slice(0, -1))
      Vue.delete(parentState, path[path.length - 1])
    })
    resetStore(this)
  }

  hasModule (path) {
    if (typeof path === 'string') path = [path]

    if (__DEV__) {
      assert(Array.isArray(path), `module path must be a string or an Array.`)
    }

    return this._modules.isRegistered(path)
  }

  hotUpdate (newOptions) {
    this._modules.update(newOptions)
    resetStore(this, true)
  }

  _withCommit (fn) {
    const committing = this._committing
    this._committing = true
    fn()
    this._committing = committing
  }
}

function genericSubscribe (fn, subs, options) {
  if (subs.indexOf(fn) < 0) {
    options && options.prepend
      ? subs.unshift(fn)
      : subs.push(fn)
  }
  return () => {
    const i = subs.indexOf(fn)
    if (i > -1) {
      subs.splice(i, 1)
    }
  }
}

function resetStore (store, hot) {
  store._actions = Object.create(null)
  store._mutations = Object.create(null)
  store._wrappedGetters = Object.create(null)
  store._modulesNamespaceMap = Object.create(null)
  const state = store.state
  // init all modules
  installModule(store, state, [], store._modules.root, true)
  // reset vm
  resetStoreVM(store, state, hot)
}
//这个方法主要是重置一个私有的 _vm 对象，它是一个 Vue 的实例。
//这个 _vm 对象会保留我们的 state 树，以及用计算属性的方式存储了 store 的 getters。来具体看看它的实现过程。
function resetStoreVM (store, state, hot) {
  //这部分留了现有的 store._vm 对象，接着遍历 store._wrappedGetters 对象，
  //在遍历过程中，依次拿到每个 getter 的包装函数，并把这个包装函数执行的结果用 computed 临时变量保存。
  //接着用 es5 的 Object.defineProperty 方法为 store.getters 定义了 get 方法，
  //也就是当我们在组件中调用this.$store.getters.xxxgetters 这个方法的时候，会访问 store._vm[xxxgetters]
  const oldVm = store._vm

  // bind store public getters
  store.getters = {}
  // reset local getters cache
  store._makeLocalGettersCache = Object.create(null)
  const wrappedGetters = store._wrappedGetters
  const computed = {}
  forEachValue(wrappedGetters, (fn, key) => {
    computed[key] = partial(fn, store)
    Object.defineProperty(store.getters, key, {
      get: () => store._vm[key],
      enumerable: true // for local getters
    })
  })

  //这部分的代码首先先拿全局 Vue.config.silent 的配置，然后临时把这个配置设成 true，
  //接着实例化一个 Vue 的实例，把 store 的状态树 state 作为 data 传入，
  //把我们刚才的临时变量 computed 作为计算属性传入。然后再把之前的 silent 配置重置。
  //设置 silent 为 true 的目的是为了取消这个 _vm 的所有日志和警告。把 computed 对象作为 _vm 的 computed 属性，这样就完成了 getters 的注册。
  //因为当我们在组件中访问this.$store.getters.xxxgetters 的时候，就相当于访问 store._vm[xxxgetters]，
  //也就是在访问 computed[xxxgetters]，这样就访问到了 xxxgetters 对应的回调函数了
  const silent = Vue.config.silent
  Vue.config.silent = true
  store._vm = new Vue({
    data: {
      $$state: state
    },
    computed
  })
  Vue.config.silent = silent

  // enable strict mode for new vm
  if (store.strict) {
    enableStrictMode(store)
  }
  //这里的逻辑很简单，由于这个函数每次都会创建新的 Vue 实例并赋值到 store._vm 上，那么旧的 _vm 对象的状态设置为 null，并调用 $destroy 方法销毁这个旧的 _vm 对象。
  if (oldVm) {
    if (hot) {
      // dispatch changes in all subscribed watchers
      // to force getter re-evaluation for hot reloading.
      store._withCommit(() => {
        oldVm._data.$$state = null
      })
    }
    Vue.nextTick(() => oldVm.$destroy())
  }
}
//store 表示当前 Store 实例，rootState 表示根 state，
//path 表示当前嵌套模块的路径数组，module 表示当前安装的state、actions、mutations、getters 以及嵌套的 modules模块
//hot 当动态改变 modules 或者热更新的时候为 true
function installModule (store, rootState, path, module, hot) {
  //通过 path 数组的长度判断是否为根
  const isRoot = !path.length
  const namespace = store._modules.getNamespace(path)

  // register in namespace map
  if (module.namespaced) {
    if (store._modulesNamespaceMap[namespace] && __DEV__) {
      console.error(`[vuex] duplicate namespace ${namespace} for the namespaced module ${path.join('/')}`)
    }
    store._modulesNamespaceMap[namespace] = module
  }

  // set state 判断当不为根且非热更新的情况，然后设置级联状态
  if (!isRoot && !hot) {
    //就是根据 path 查找 state 上的嵌套 state。在这里就是传入 rootState 和 path，计算出当前模块的父模块的 state，
    //由于模块的 path 是根据模块的名称 concat 连接的，所以 path 的最后一个元素就是当前模块的模块名
    const parentState = getNestedState(rootState, path.slice(0, -1))
    const moduleName = path[path.length - 1]
    //把当前模块的 state 添加到 parentState 中。
    //Vuex 中所有对 state 的修改都会用 _withCommit函数包装，保证在同步修改 state 的过程中 this._committing 的值始终为true。
    //这样当我们观测 state 的变化时，如果 this._committing 的值不为 true，则能检查到这个状态修改是有问题的
    store._withCommit(() => {
      if (__DEV__) {
        if (moduleName in parentState) {
          console.warn(
            `[vuex] state field "${moduleName}" was overridden by a module with the same name at "${path.join('.')}"`
          )
        }
      }
      Vue.set(parentState, moduleName, module.state)
    })
  }

  const local = module.context = makeLocalContext(store, namespace, path)
  //将传入的options对象即（mutations、actions、getters）进行注册
  module.forEachMutation((mutation, key) => {
    const namespacedType = namespace + key
    registerMutation(store, namespacedType, mutation, local)
  })

  module.forEachAction((action, key) => {
    const type = action.root ? key : namespace + key
    const handler = action.handler || action
    registerAction(store, type, handler, local)
  })

  module.forEachGetter((getter, key) => {
    const namespacedType = namespace + key
    registerGetter(store, namespacedType, getter, local)
  })
  /**
   * 因为 Vuex 本身是单一状态树，应用的所有状态都包含在一个大对象内，随着我们应用规模的不断增长，这个 Store 变得非常臃肿。
   * 为了解决这个问题，Vuex 允许我们把 store 分 module（模块）。
   * 每一个模块包含各自的 state、mutations、actions 和 getters，甚至是嵌套模块
   * 递归调用 installModule 去安装子模块
   * 和刚才不同的是，path 不为空，module 对应为子模块
  */
  module.forEachChild((child, key) => {
    installModule(store, rootState, path.concat(key), child, hot)
  })
}

/**
 * make localized dispatch, commit, getters and state
 * if there is no namespace, just use root ones
 */
function makeLocalContext (store, namespace, path) {
  const noNamespace = namespace === ''

  const local = {
    dispatch: noNamespace ? store.dispatch : (_type, _payload, _options) => {
      const args = unifyObjectStyle(_type, _payload, _options)
      const { payload, options } = args
      let { type } = args

      if (!options || !options.root) {
        type = namespace + type
        if (__DEV__ && !store._actions[type]) {
          console.error(`[vuex] unknown local action type: ${args.type}, global type: ${type}`)
          return
        }
      }

      return store.dispatch(type, payload)
    },

    commit: noNamespace ? store.commit : (_type, _payload, _options) => {
      const args = unifyObjectStyle(_type, _payload, _options)
      const { payload, options } = args
      let { type } = args

      if (!options || !options.root) {
        type = namespace + type
        if (__DEV__ && !store._mutations[type]) {
          console.error(`[vuex] unknown local mutation type: ${args.type}, global type: ${type}`)
          return
        }
      }

      store.commit(type, payload, options)
    }
  }

  // getters and state object must be gotten lazily
  // because they will be changed by vm update
  Object.defineProperties(local, {
    getters: {
      get: noNamespace
        ? () => store.getters
        : () => makeLocalGetters(store, namespace)
    },
    state: {
      get: () => getNestedState(store.state, path)
    }
  })

  return local
}

function makeLocalGetters (store, namespace) {
  if (!store._makeLocalGettersCache[namespace]) {
    const gettersProxy = {}
    const splitPos = namespace.length
    Object.keys(store.getters).forEach(type => {
      // skip if the target getter is not match this namespace
      if (type.slice(0, splitPos) !== namespace) return

      // extract local getter type
      const localType = type.slice(splitPos)

      // Add a port to the getters proxy.
      // Define as getter property because
      // we do not want to evaluate the getters in this time.
      Object.defineProperty(gettersProxy, localType, {
        get: () => store.getters[type],
        enumerable: true
      })
    })
    store._makeLocalGettersCache[namespace] = gettersProxy
  }

  return store._makeLocalGettersCache[namespace]
}

/**
 * mutation 的作用就是同步修改当前模块的 state ，函数首先通过 type 拿到对应的 mutation 对象数组， 然后把一个 mutation 的包装函数 push 到这个数组中，
 * 这个函数接收一个参数 payload，这个就是我们在定义 mutation 的时候接收的额外参数。
 * 这个函数执行的时候会调用 mutation 的回调函数，并通过local得到当前模块的 state，和 playload 一起作为回调函数的参数
 * 
*/
function registerMutation (store, type, handler, local) {
  const entry = store._mutations[type] || (store._mutations[type] = [])
  entry.push(function wrappedMutationHandler (payload) {
    handler.call(store, local.state, payload)
  })
}
/**
 * registerAction 是对 store 的 action 的初始化，它和 registerMutation 的参数一致，
 * 和 mutation 不同一点，mutation 是同步修改当前模块的 state，而 action 是可以异步去修改 state，
 * 这里不要误会，在 action 的回调中并不会直接修改 state ，仍然是通过提交一个 mutation 去修改 state（在 Vuex 中，mutation 是修改 state 的唯一途径）。
 * 那我们就来看看 action 是如何做到这一点的。
 * 函数首先也是通过 type 拿到对应 action 的对象数组，然后把一个 action 的包装函数 push 到这个数组中，
 * 这个函数接收 1 个参数，payload 表示额外参数 。
 * 这个函数执行的时候会调用 action 的回调函数，传入一个 context 对象，
 * 这个对象包括了 store 的 commit 和 dispatch 方法、getter、当前模块的 state 和 rootState 等等。
 * 接着对这个函数的返回值做判断，如果不是一个 Promise 对象，则调用 Promise.resolve（res） 给res 包装成了一个 Promise 对象。
 * 这里也就解释了为何 Vuex 的源码依赖 Promise，这里对 Promise 的判断也和简单，参考代码 src/util.js，
*/
function registerAction (store, type, handler, local) {
  const entry = store._actions[type] || (store._actions[type] = [])
  entry.push(function wrappedActionHandler (payload) {
    let res = handler.call(store, {
      dispatch: local.dispatch,
      commit: local.commit,
      getters: local.getters,
      state: local.state,
      rootGetters: store.getters,
      rootState: store.state
    }, payload)
    if (!isPromise(res)) {
      res = Promise.resolve(res)
    }
    //当用到 Vuex devtools 开启的时候，我们才能捕获 promise 的过程中的 err
    if (store._devtoolHook) {
      return res.catch(err => {
        store._devtoolHook.emit('vuex:error', err)
        throw err
      })
    } else {
      return res
    }
  })
}

function registerGetter (store, type, rawGetter, local) {
  if (store._wrappedGetters[type]) {
    if (__DEV__) {
      console.error(`[vuex] duplicate getter key: ${type}`)
    }
    return
  }
  store._wrappedGetters[type] = function wrappedGetter (store) {
    return rawGetter(
      local.state, // local state
      local.getters, // local getters
      store.state, // root state
      store.getters // root getters
    )
  }
}
//严格模式做的事情很简单，监测 store._vm.state 的变化，看看 state 的变化是否通过执行 mutation 的回调函数改变，
//如果是外部直接修改 state，那么 store._committing 的值为 false，这样就抛出一条错误。再次强调一下，Vuex 中对 state 的修改只能在 mutation 的回调函数里。
function enableStrictMode (store) {
  store._vm.$watch(function () { return this._data.$$state }, () => {
    if (__DEV__) {
      assert(store._committing, `do not mutate vuex store state outside mutation handlers.`)
    }
  }, { deep: true, sync: true })
}

function getNestedState (state, path) {
  return path.reduce((state, key) => state[key], state)
}

function unifyObjectStyle (type, payload, options) {
  if (isObject(type) && type.type) {
    options = payload
    payload = type
    type = type.type
  }

  if (__DEV__) {
    assert(typeof type === 'string', `expects string as the type, but found ${typeof type}.`)
  }

  return { type, payload, options }
}

//我们实现了一个 install 方法，这个方法当我们全局引用 Vue ，也就是 window 上有 Vue 对象的时候，会手动调用 install 方法，并传入 Vue 的引用；
export function install (_Vue) {
  if (Vue && _Vue === Vue) {
    if (__DEV__) {
      console.error(
        '[vuex] already installed. Vue.use(Vuex) should be called only once.'
      )
    }
    return
  }
  Vue = _Vue
  applyMixin(Vue)
}

/**
 * 当 Vue 通过 npm 安装到项目中的时候，我们在代码中引入第三方 Vue 插件通常会编写如下代码：
 * 
 * import Vue from 'vue'
 * import Vuex from 'vuex'
 * ...
 * Vue.use(Vuex)
 * 
 * 当我们执行 Vue.use(Vuex) 这句代码的时候，实际上就是调用了 install 的方法并传入 Vue 的引用
*/